ZK10 Preview: Using the new and light Client MVVM
James Chu, Engineer, Potix Corporation
May 26, 2022 (last update Oct 19, 2023)
10.0.0-Beta or later
Introduction
One of the major enhancements of ZK 10 is the Client MVVM. Without creating lots of binding objects on the server-side, client MVVM helps to reduce the memory footprint significantly. You can view the performance testing result in this article ZK10_Preview:_30x_Lighter_and_300x_Faster_with_Client_MVVM.
When we use ZK (server) MVVM, we start from a ZUL file and apply "BindComposer" to a component. It will create a binder, data bindings, and initialize ViewModel instances on the server-side. The binder is the kernel of MVVM and acts as a bridge to transfer data and events between the View and ViewModel. See more detail here.
The new Client MVVM also starts from a ZUL and a ViewModel, and the binding information will be passed to the client-side with ZK widgets. When the widgets are created and rendered on the browser, the bindings will also be created at the same time.
As you can see, the binding now happens on the client-side. However, from an application developer's point of view, this change is transparent, and it is something ZK takes care of for you. You'll still be building the MVVM application in the same way, referencing our ZK MVVM guide, except for a few differences which we will cover in the following sections.
Now, let's take a look at how we can enable the Client MVVM.
Setting up Client MVVM
There are two ways to use the client MVVM, either use it against a specific ViewModel, or, set it globally. Either way, you have to first include the jar file and add the listener.
Step 1
Use ZK 10 EE-Eval freshly, and include the client-bind jar file.
Step 2
Add the listener for Client MVVM. (in zk.xml)
<listener>
<listener-class>org.zkoss.clientbind.BinderPropertiesRenderer</listener-class>
</listener>
Step 3
Option 1: Apply to a ViewModel
Apply "org.zkoss.zephyrex.bind.ClientBindComposer" in ZUL.
For example: (in ZUL File)
<div apply="org.zkoss.clientbind.ClientBindComposer" viewModel="..." >
<!-- other components -->
</div>
Option 2: Set it globally
We can change the default auto-applied org.zkoss.bind.BindComposer:
(in zk.xml)
<library-property>
<name>org.zkoss.bind.defaultComposer.class</name>
<value>org.zkoss.clientbind.ClientBindComposer</value>
</library-property>
(in ZUL File)
<div viewModel="..." >
<!-- other components -->
</div>
If you use the global method, the ClientBindComposer will be applied automatically. If you wish to use a different composer (ex. the traditional ZK MVVM) on a specific VM, you can specify the "apply" attribute on that VM with the corresponding composer, and the manually specified apply will have the priority.
Once it is correctly configured, we can start to enjoy the benefits of client MVVM!
Restrictions and Differences
With client MVVM, the MVVM mechanism is now done at the client-side instead of the server-side, there are several limitations and differences compared to the server-side MVVM.
1. EL expression is not supported
EL expression - ${expr} and EL3 are not supported. For Example the following expressions are not supported:
<label value="${expr}" />
<label value="@load(('Hi, ' += vm.person.firstName += ' ' += vm.person.lastName))" />
<label value="@load((vm.names.stream().filter(x -> x.contains(vm.filter)).toList()))" />
2. Follow the MVVM pattern
The main feature of MVVM is to decouple the UI and non-UI code, which means components should be controlled in MVVM. Developers should avoid controlling components directly. Even though we have this guideline, there are cases where developers have been accessing the components directly, and they use to work in ZK MVVM.
But now, when you apply client MVVM, ZK will update all the binding evaluation results into widget properties. This means the server-side does not have the full and most up-to-date information. Therefore, you cannot, and should no longer access and control those child components as a server-side component. You have to stick to the MVVM pattern.
- Traversing components, like using "self" and ".parent" are not supported.
<button onClick="@command('delete', index=self.parent.parent.index)"/>
- @Listen, @Wire and @SelectorParam are not supported.
- You cannot use @BindingParam and @ContextParam to get components.
@Command
public void commandA(@BindingParam Component otherComponent,
@ContextParam(ContextType.COMPONENT) Component targetComponent) {
// do something here.
}
Note that even though you cannot get the full component, you CAN still use @BindingParam to retrieve values and other "ContextType"s.
<button label="click it" onClick="@command('commandB', foo='something', bar='somethingelse')" />
@Command
public void commandB(@BindingParam("foo") String foo,
@ContextParam(ContextType.PAGE) Page page,
@ContextParam(ContextType.DESKTOP) Desktop desktop) {
// do something here.
}
- Component related API might return null (no component on the server-side).
@Command
public void commandB(@ContextParam(ContextType.TRIGGER_EVENT) MouseEvent event) {
// event.getTarget() is null.
// event.getPageX()/event.getPageY()/.. are still available.
}
// the component is null
org.zkoss.bind.Converter#coerceToUi(B beanProp, C component, BindContext ctx);
org.zkoss.bind.Converter#coerceToBean(U compAttr, C component, BindContext ctx);
3. Getter Method should be pure in View Model
To write those data in the view model to the client side, client MVVM depends on the getter methods to retrieve data.
For example, the return value of a getter method should not be always a "new" object.
4. Deferred Binding is no longer supported
Deferred Binding is no longer supported. This feature is to avoid unnecessary AU requests (data updates). Since client MVVM does those bindings on the client-side, it doesn't require such an update.
<textbox value="@bind(vm.text1)">
<custom-attributes org.zkoss.bind.event.deferPost="false"/>
</textbox>
5. SmartNotifyChange always on
SmartNotifyChange is always enabled in Client MVVM, which means that the properties will only update (reload) when the value of the expression is changed.
6. Conditional Binding works differently
Conditional Binding works differently when the command updates the value without doing "NotifyChange". For example:
<label value="@load(vm.text, after='doChange')" />
<button label="Do Change" onClick="@command('doChange')" />
public String text = "123";
@Command
public void doChange() {
this.text += "changed";
}
In ZK MVVM, the value of Label will be "123changed" after the button - "Do Change" is clicked.
In client MVVM, the value will remain "123". If you intend to see "123changed" you will need to do NotifyChange.
7. AnnotateDataBinder and Calling Binder API are no longer supported
AnnotateDataBinder is the old ZK binding in zkplus module. And the Binder API is for server MVVM. For example:
<window id="myWin" viewModel="@id('vm') @init('...')">
<button id="changeNameBtn" label="change name">
<attribute name="onClick"><![CDATA[
myWin$composer.getViewModel().setName("...");
myWin$composer.getBinder().loadComponent(myWin, true);
]]></attribute>
</button>
</window>
Or using custom Binder
<window viewModel="..." binder="@id('mybinder') @init(vm.binder)">
</window>
8. More Information
For migration, you can use ZK Client MVVM Linter to check your ZK MVVM project.
Debugging Tips
Unsupported/incompatible usages mentioned in the previous section will be reported as system logs during the application startup, remember to check the logs when running into issues using the client MVVM.
Upgrade Tips
- Upgrading your ZK MVVM project to Client MVVM is straightforward if you do not run across any of the unsupported usages mentioned above.
- Client MVVM can be applied to a specific VM if you are not ready to use it globally.
- You may wish to update your code and remove the incompatibility if the performance/memory benefit client MVVM brings is important in your use case.
- Client MVVM is a pluggable feature, if you are not ready to use Client MVVM, no worries, you can still upgrade to ZK 10 and use the existing ZK MVVM.
Summary
The new ZK 10's Client MVVM makes ZK application lighter and faster. In some in-house test cases where a large number of components are used, we are seeing 30 to 300 times improvements. We hope it also greatly improves the performance and usability of your project.
Try it out today and let us know what you think!
For more ZK 10 articles, visit here.
Comments
Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License. |